package com.huilian.tool.common.utils;

import com.beust.jcommander.internal.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.MultiValueMap;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author chenqiwen
 * @CreateDate: 2022-04-11-16-30
 * @Description:
 */
public class SignUtil {

    private static String[] signKey = new String[]{
            "app_key",
            "timestamp",
            "format",
            "v",
            "sign_method",
            "session",
            "open_shop_uuid",
            "mobile",
            "deal_group_id",
            "deal_id",
            "offset",
            "limit",
            "platform",
            "page_no",
            "page_size",
            "type",
            "biz_type",
            "date",
            "receipt_code",
            "app_shop_accountname",
            "app_shop_account",
            "app_deal_id",
            "count",
            "requestid",
            "qr_code",
    };

    /**
     * 签名算法 MultiValueMap
     */
    public static String generateSign(MultiValueMap<String, String> params, String secret, String signMethod) throws IOException {
        // 第一步：检查参数是否已经排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        // 第二步：把所有参数名和参数值串在一起
        StringBuilder query = new StringBuilder();
        if ("MD5".equals(signMethod)) {
            query.append(secret);
        }

        for (String key : keys) {
            String value = params.get(key).get(0);
            if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(value)) {
                query.append(key).append(value);
            }
        }

        // 第三步：使用MD5/HMAC加密
        byte[] bytes = new byte[0];
        if ("HmacMD5".equals(signMethod)) {
            bytes = encryptHMAC(query.toString(), secret);
        } else {
            query.append(secret);
            try {
                bytes = encryptMD5(query.toString());
            } catch (NoSuchAlgorithmException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        // 第四步：把二进制转化为大写的十六进制
        return byte2hex(bytes);
    }

    /**
     * 签名算法 Map
     */
    public static String generateSign(Map<String, String> params, String appSecret, String signMethod) {
        // 第一步：参数排序
        List<String> keys = Lists.newArrayList();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (StringUtils.isNotEmpty(entry.getValue())) {
                keys.add(entry.getKey());
            }
        }
        Collections.sort(keys);
        // 第二步：把所有参数名和参数值串在一起
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotEmpty(appSecret)) {
            sb.append(appSecret);
        }
        for (String key : keys) {
            sb.append(key).append(params.get(key).trim());
        }
        if (StringUtils.isNotEmpty(appSecret)) {
            sb.append(appSecret);
        }
        String encryptionKey = sb.toString().trim();
        // 第三步：加签
        if (signMethod.equals("MD5")) {
            try {
                String sign = genMd5(encryptionKey);
                return sign;
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }else{
            //开发者暂不需支持，支持MD5即可
            return "";
        }
    }

    public static String genMd5(String info) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] infoBytes = info.getBytes();
        md5.update(infoBytes);
        byte[] sign = md5.digest();
        return byteArrayToHex(sign);
    }

    public static String byteArrayToHex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toLowerCase());
        }
        return sign.toString();
    }


    public static byte[] encryptHMAC(String data, String secret) throws IOException {
        byte[] bytes = null;
        try {
            SecretKey secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacMD5");
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data.getBytes("UTF-8"));
        } catch (GeneralSecurityException gse) {
            throw new IOException(gse.toString());
        }
        return bytes;
    }

    public static byte[] encryptMD5(String info) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] infoBytes = info.getBytes();
        md5.update(infoBytes);
        return md5.digest();
    }

    public static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toLowerCase());
        }
        return sign.toString();
    }


    public static String mapToGetParam(Map<String, String> map) throws IOException{
        StringBuffer sb = new StringBuffer();
        for (String key : map.keySet()) {
            sb.append("&");
            sb.append(key).append("=");
            String value = map.get(key);
            value = (value == null ? "" : value);
            sb.append(URLEncoder.encode(value, "UTF-8"));
        }

        return sb.substring(1).toString();
    }
}
